//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	ManagedDisposableCache.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Web;
using System.Web.UI;

namespace Smart.Common
{
	[DebuggerStepThrough]
	public static class ManagedDisposableCache
	{
		/// <summary>
		/// Need to ensure static fields filled. DO NOT REMOVE!
		/// </summary>
		static ManagedDisposableCache()
		{
		}

		private static int _maxDisposableCount = 10;
		private static bool _listSorted;
		private static ReaderWriterLockSlim _syncLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

		public static SyncDictionary<string, ManagedDisposableInfo> GetMap()
		{
			return
				(SyncDictionary<string, ManagedDisposableInfo>)
				SPServices.Cache.GetRequestItem(Const.Cache.DisposableMapKey,
												() =>
												new SyncDictionary<string, ManagedDisposableInfo>(StringComparer.OrdinalIgnoreCase));
		}

		public static List<ManagedDisposableInfo> GetList()
		{
			return
				(List<ManagedDisposableInfo>)
				SPServices.Cache.GetRequestItem(Const.Cache.DisposableListKey,
												() =>
												new List<ManagedDisposableInfo>());
		}

		public static ManagedDisposableInfo Find(string key)
		{
			if (key.IsNullOrEmpty())
				throw new ArgumentException();

			_syncLock.EnterReadLock();

			try
			{
				var map = GetMap();

				return map.SafeGet(key);
			}
			finally
			{
				_syncLock.ExitReadLock();
			}
		}

		public static void Register(ManagedDisposableInfo info)
		{
			if (info == null)
				throw new ArgumentNullException("info");

			_syncLock.EnterWriteLock();

			try
			{
				if (!_endRequestHandlerRegistered)
				{
					var ctx = HttpContext.Current;

					if (ctx != null && ctx.Handler != null)
					{
						try
						{
							var page = ctx.Handler as Page;

							if (page != null)
							{
								page.Unload += ApplicationInstanceEndRequest;

								_endRequestHandlerRegistered = true;
							}

							if (!_endRequestHandlerRegistered && ctx.ApplicationInstance != null)
							{
								ctx.ApplicationInstance.EndRequest += ApplicationInstanceEndRequest;

								_endRequestHandlerRegistered = true;
							}
						}
						catch (Exception x)
						{
							H.LX("RegisterRequestHandler", x);
						}
					}
				}

				var map = GetMap();
				var list = GetList();

				_listSorted = false;

				list.Add(info);

				foreach (var key in info.Keys)
					map[key] = info;
			}
			finally
			{
				_syncLock.ExitWriteLock();
			}
		}

		private static bool _endRequestHandlerRegistered;

		private static void ApplicationInstanceEndRequest(object sender, EventArgs e)
		{
			try
			{
				_syncLock.EnterWriteLock();

				try
				{
					var map = GetMap();
					var list = GetList();

					if (list.Count > _maxDisposableCount)
					{
						H.LT("ManagedDisposable.Exception: disposable count = {0} (limit: {1})", list.Count, _maxDisposableCount);
					}

					foreach (var info in list)
					{
						info.Release();
					}

					map.Clear();
					list.Clear();
				}
				finally
				{
					_syncLock.ExitWriteLock();
				}
			}
			catch (Exception x)
			{
				H.LX("ApplicationInstanceEndRequest", x);
			}
		}

		public static void Unregister(ManagedDisposableInfo info)
		{
			if (info == null)
				throw new ArgumentNullException("info");

			_syncLock.EnterWriteLock();

			try
			{
				var map = GetMap();
				var list = GetList();

				list.Remove(info);

				foreach (var key in info.Keys)
					map.Remove(key);
			}
			finally
			{
				_syncLock.ExitWriteLock();
			}
		}

		public static void CheckRelease(ManagedDisposableInfo info)
		{
			_syncLock.EnterWriteLock();

			try
			{
				if (info.DecrementRef() <= 0)
				{
					var map = GetMap();
					var list = GetList();

					if (_endRequestHandlerRegistered && list.Count <= _maxDisposableCount)
						return;

					if (!_listSorted)
					{
						list.Sort((d1, d2) => d1.UseCount - d2.UseCount);

						_listSorted = true;
					}

#if FREE_ALL_EMPTY
					var freeCount = list.Count - _maxDisposableCount;

					while (freeCount-- > 0)
					{
						var idx = list.FindLastIndex(einfo => einfo.RefCount == 0 && (einfo == info || einfo.ShouldBeDisposed));

						if (idx < 0)
							break;

						var dinfo = list[idx];

						dinfo.Release();

						list.RemoveAt(idx);

						foreach (var key in dinfo.Keys)
							map.Remove(key);
					}
#else
					var idx = list.IndexOf(info);

					if (idx >= _maxDisposableCount)
					{
						info.Release();

						list.RemoveAt(idx);

						foreach (var key in info.Keys)
							map.Remove(key);
					}
#endif
				}
			}
			finally
			{
				_syncLock.ExitWriteLock();
			}
		}
	}
}